#pragma once

#include <atomic>
#include <cctype>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <unordered_map>

const int TASK_MAX_THRESHHOLD = INT32_MAX;
const int THREAD_MAX_THRESHHOLD = 1024;
const int THREAD_MAX_IDLE_TIME = 60;  // 单位：秒

enum class PoolMode { MODE_FIXED, MODE_CACHED };

class Thread {
public:
    using ThreadFunc = std::function<void(int)>;

    Thread(ThreadFunc func) : func_(func), threadId_(generateId_++) {
    }

    ~Thread() = default;

    void start() {
        std::thread t(func_, threadId_);
        t.detach();
    }

    int getId() const {
        return threadId_;
    }

private:
    ThreadFunc func_;
    static int generateId_;
    int threadId_;
};

int Thread::generateId_ = 0;

class ThreadPool {
public:
    ThreadPool()
        : initThreadSize_(0),
          threadSizeThreshhold_(THREAD_MAX_THRESHHOLD),
          idleThreadSize_(0),
          taskSizeThreshhold_(TASK_MAX_THRESHHOLD),
          mode_(PoolMode::MODE_FIXED),
          isPoolRunning_(false) {
    }

    ~ThreadPool() {
        isPoolRunning_ = false;

        std::unique_lock<std::mutex> lock(mtx_);
        notEmpty_.notify_all();
        exitCond_.wait(lock, [&]() -> bool { return threads_.size() == 0; });
    }

    void setMode(PoolMode mode) {
        if (isPoolRunning_) return;
        mode_ = mode;
    }

    void setTaskQueMaxThreshHold(int threshhold) {
        if (isPoolRunning_) return;
        taskSizeThreshhold_ = threshhold;
    }

    void setThreadSizeThreshHold(int threshhold) {
        if (isPoolRunning_) return;
        if (mode_ == PoolMode::MODE_CACHED) {
            threadSizeThreshhold_ = threshhold;
        }
    }

    void start(int initThreadSize = std::thread::hardware_concurrency()) {
        isPoolRunning_ = true;
        initThreadSize_ = initThreadSize;

        for (int i = 0; i < initThreadSize_; i++) {
            auto ptr = std::make_unique<Thread>(std::bind(
                &ThreadPool::threadFunc, this, std::placeholders::_1));
            int threadId = ptr->getId();
            threads_.emplace(threadId, std::move(ptr));
        }

        for (int i = 0; i < initThreadSize_; i++) {
            threads_[i]->start();
            idleThreadSize_++;
        }
    }

    template <typename Func, typename... Args>
    auto submit(Func&& func, Args&&... args)
        -> std::future<decltype(func(args...))> {
        using RType = decltype(func(args...));
        auto task = std::make_shared<std::packaged_task<RType()>>(
            std::bind(std::forward<Func>(func), std::forward<Args>(args)...));
        std::future<RType> result = task->get_future();

        std::unique_lock<std::mutex> lock(mtx_);
        if (!notFull_.wait_for(lock, std::chrono::seconds(1), [&]() -> bool {
                return tasks_.size() < (size_t)taskSizeThreshhold_;
            })) {
            auto task = std::make_shared<std::packaged_task<RType()>>(
                []() -> RType { return RType(); });
            (*task)();
            return task->get_future();
        }

        tasks_.emplace([task]() { (*task)(); });
        notEmpty_.notify_all();

        if (mode_ == PoolMode::MODE_CACHED && tasks_.size() > idleThreadSize_ &&
            threads_.size() < threadSizeThreshhold_) {
            auto ptr = std::make_unique<Thread>(std::bind(
                &ThreadPool::threadFunc, this, std::placeholders::_1));
            int threadId = ptr->getId();
            threads_.emplace(threadId, std::move(ptr));
            threads_[threadId]->start();
            idleThreadSize_++;
        }
        return result;
    }

    ThreadPool(const ThreadPool&) = delete;
    ThreadPool& operator=(const ThreadPool&) = delete;

private:
    void threadFunc(int threadId) {
        auto lastTime = std::chrono::high_resolution_clock().now();
        for (;;) {
            Task task;
            {
                std::unique_lock<std::mutex> lock(mtx_);
                while (tasks_.size() == 0) {
                    if (!isPoolRunning_) {
                        threads_.erase(threadId);
                        exitCond_.notify_all();
                        return;
                    }

                    if (mode_ == PoolMode::MODE_CACHED) {
                        if (std::cv_status::timeout ==
                            notEmpty_.wait_for(lock, std::chrono::seconds(1))) {
                            auto now =
                                std::chrono::high_resolution_clock().now();
                            auto dur = std::chrono::duration_cast<
                                std::chrono::seconds>(now - lastTime);
                            if (dur.count() >= THREAD_MAX_IDLE_TIME &&
                                threads_.size() > initThreadSize_) {
                                threads_.erase(threadId);
                                idleThreadSize_--;
                                return;
                            }
                        }
                    } else {
                        notEmpty_.wait(lock);
                    }
                }

                idleThreadSize_--;

                task = tasks_.front();
                tasks_.pop();

                if (tasks_.size() > 0) {
                    notEmpty_.notify_all();
                }

                notFull_.notify_all();
            }

            if (task != nullptr) {
                task();
            }

            idleThreadSize_++;
            lastTime = std::chrono::high_resolution_clock().now();
        }
    }

private:
    std::unordered_map<int, std::unique_ptr<Thread>> threads_;
    int initThreadSize_;
    int threadSizeThreshhold_;
    std::atomic_int idleThreadSize_;

    using Task = std::function<void()>;
    std::queue<Task> tasks_;
    int taskSizeThreshhold_;

    std::mutex mtx_;
    std::condition_variable notFull_;
    std::condition_variable notEmpty_;
    std::condition_variable exitCond_;

    PoolMode mode_;
    std::atomic_bool isPoolRunning_;
};
